多线程基础之七:多线程遇上printf的“延迟写”策略

您所在的位置:网站首页 printf 线程安全 多线程基础之七:多线程遇上printf的“延迟写”策略

多线程基础之七:多线程遇上printf的“延迟写”策略

2024-06-02 10:15| 来源: 网络整理| 查看: 265

0. 运行库提供的IO读写函数采用“延迟写”策略的原因 编程时经常会用到printf()函数,但是由于printf()函数涉及到和显示器或磁盘等外设进行交互,所以操作涉及到从“用户态–>内核态–>返回用户态”的一系列内核转换过程,但是从用户态通过中断使用系统调用涉及到内核从用户态切换到内核态,上下文切换是间很费时的操作。更糟糕的是,如果printf()的目标设备是显示器这种字符设备(单次传输一个字节),那么显然不能每输入一个字节就通过中断调用系统API输出一个字节,这显然会导致CPU浪费掉大量的时间在无意义的上下文切换(内核态转换)。

再一次的参考IT的万能定律—银弹,如果一个问题不好解决,那就加一层抽象层。所以平常使用的printf()函数并非是操作系统直接提供的API,而是由运行库在操作系统API (如write()系统调用 )基础上封装来的函数。简单地说,运行库为IO读写添加的一层抽象层的基本策略是在系统调用( read()或write() )基础上额外配备缓冲区和采用“延迟写”策略,如果你对细节感兴趣可以参考我的另一篇文章——运行库:Windows下MSVC CRT运行库封装fread()函数解析。其中printf()函数的”批量预读“和”延迟写“策略在单线程的环境下并不会影响我们的正常使用,但是printf()函数的”延迟写“碰上多线程环境则可能让最后输出结果并不能按照预想的那样。

以printf函数为例,”延迟写“策略思想是指用户态下调用printf()函数,除非满足以下四种条件,否则并不立即从用户态转入内核态调用系统API输出,而是先保存在缓冲区(缓冲操作依旧运行在用户态下,故而免去了内核切换状态),直到满足以下四种条件,才切换到内核态,进行集中输出 1. 缓冲区填满; 2. 写入的字符中有”\n” “\r”; 3. 调用fflush手动刷新缓冲区; 4. 调用scanf从缓冲区中立即读取数据,也会隐式调用fflush。 通过以下的小程序,可以测试在在Linux系统上为IO写操作配备的缓冲区大小 printfBuffer.cpp

#include #include int main (int argc, char* argv[]) { int i = 0x61; //0x61是a的ACSCII码 while (1) { printf("%c", i); i++; i = (i - 0x61) % 26 + 0x61; usleep(1000); } return 0; }

在屏幕第一次输出信息时,立即按下crtl+C,然后数一数屏幕中的字母个数便可以判断buffer大小,可以数一数,buffer_size = 1024 byte。这个程序改装后在Windows下运行并不会得到合适的效果(Windows下是在实时输出的,没有Linux下明显的停顿后一次性刷出一屏幕的效果,然后过一会再刷出一屏幕)。 这里写图片描述

1.printf()函数的“延迟写”策略和多线程的碰撞 multiple_thread_use_printf.cpp

#include #include using namespace std; DWORD WINAPI ThreadProc1(__in LPVOID lpParameter); DWORD WINAPI ThreadProc2(__in LPVOID lpParameter); int main() { HANDLE hThread[2] = {0}; hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL); hThread[1] = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL); WaitForMultipleObjects(2,hThread,TRUE,INFINITE); for (int i=0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3